﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.IO;

namespace Tickz.Commonz.Json
{
    public class JSON
    {
        public readonly static JSON Instance = new JSON();

        private JSON()
        {

        }

        public string ToJSON(object obj)
        {
            return new JSONSerializer().ConvertToJSON(obj);
        }
        public string ToJSON(object obj, int isDictionaryForOne)
        {
            JSONSerializer jSONSerializer = new JSONSerializer();
            jSONSerializer.IsDictionaryForOne = isDictionaryForOne;
            return jSONSerializer.ConvertToJSON(obj);
        }

        public object ToObject(string json)
        {
            Dictionary<string, object> ht = (Dictionary<string, object>)JsonParser.JsonDecode(json);
            if (ht == null)
                return null;
            return ParseDictionary(ht);
        }

        #region [   PROPERTY GET SET CACHE   ]
        SafeDictionary<string, Type> _typecache = new SafeDictionary<string, Type>();
        private Type GetTypeFromCache(string typename)
        {
            if (_typecache.ContainsKey(typename))
                return _typecache[typename];
            else
            {
                Type t = Type.GetType(typename);
                _typecache.Add(typename, t);
                return t;
            }
        }

        SafeDictionary<string, PropertyInfo> _propertycache = new SafeDictionary<string, PropertyInfo>();
        private PropertyInfo getproperty(Type type, string propertyname)
        {
            if (propertyname == "$type")
                return null;
            StringBuilder sb = new StringBuilder();
            sb.Append(type.Name);
            sb.Append("|");
            sb.Append(propertyname);
            string n = sb.ToString();

            if (_propertycache.ContainsKey(n))
            {
                return _propertycache[n];
            }
            else
            {
                PropertyInfo[] pr = type.GetProperties();
                foreach (PropertyInfo p in pr)
                {
                    StringBuilder sbb = new StringBuilder();
                    sbb.Append(type.Name);
                    sbb.Append("|");
                    sbb.Append(p.Name);
                    string nn = sbb.ToString();
                    if (_propertycache.ContainsKey(nn) == false)
                        _propertycache.Add(nn, p);
                }
                return _propertycache[n];
            }
        }

        private delegate void GenericSetter(object target, object value);

        private static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
        {
            MethodInfo setMethod = propertyInfo.GetSetMethod();
            if (setMethod == null)
                return null;

            Type[] arguments = new Type[2];
            arguments[0] = arguments[1] = typeof(object);

            DynamicMethod setter = new DynamicMethod(
                String.Concat("_Set", propertyInfo.Name, "_"),
                typeof(void), arguments, propertyInfo.DeclaringType);
            ILGenerator il = setter.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
            il.Emit(OpCodes.Ldarg_1);

            if (propertyInfo.PropertyType.IsClass)
                il.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
            else
                il.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);

            il.EmitCall(OpCodes.Callvirt, setMethod, null);
            il.Emit(OpCodes.Ret);

            return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
        }

        public delegate object GenericGetter(object target);

        private static GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
        {
            MethodInfo getMethod = propertyInfo.GetGetMethod();
            if (getMethod == null)
                return null;

            Type[] arguments = new Type[1];
            arguments[0] = typeof(object);

            DynamicMethod getter = new DynamicMethod(
                String.Concat("_Get", propertyInfo.Name, "_"),
                typeof(object), arguments, propertyInfo.DeclaringType);
            ILGenerator il = getter.GetILGenerator();
            il.DeclareLocal(typeof(object));
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
            il.EmitCall(OpCodes.Callvirt, getMethod, null);

            if (!propertyInfo.PropertyType.IsClass)
                il.Emit(OpCodes.Box, propertyInfo.PropertyType);

            il.Emit(OpCodes.Ret);

            return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
        }

        SafeDictionary<Type, List<Getters>> _getterscache = new SafeDictionary<Type, List<Getters>>();
        internal List<Getters> GetGetters(Type type)
        {
            if (_getterscache.ContainsKey(type))
                return _getterscache[type];
            else
            {
                PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                List<Getters> getters = new List<Getters>();
                foreach (PropertyInfo p in props)
                {
                    GenericGetter g = CreateGetMethod(p);
                    if (g != null)
                    {
                        Getters gg = new Getters();
                        gg.Name = p.Name;
                        gg.Getter = g;
                        getters.Add(gg);
                    }

                }
                _getterscache.Add(type, getters);
                return getters;
            }
        }

        SafeDictionary<PropertyInfo, GenericSetter> _settercache = new SafeDictionary<PropertyInfo, GenericSetter>();
        private GenericSetter GetSetter(PropertyInfo prop)
        {
            if (_settercache.ContainsKey(prop))
                return _settercache[prop];
            else
            {
                GenericSetter s = CreateSetMethod(prop);
                _settercache.Add(prop, s);
                return s;
            }
        }
        #endregion

        private object ParseDictionary(Dictionary<string, object> d)
        {
            string tn = "" + d["$type"];
            Type type = GetTypeFromCache(tn);
            object o = Activator.CreateInstance(type);
            foreach (string name in d.Keys)
            {
                PropertyInfo pi = getproperty(type, name);
                if (pi != null)
                {
                    object v = d[name];

                    if (v != null)
                    {
                        object oset = null;
                        GenericSetter setter;
                        Type pt = pi.PropertyType;
                        object dic = pt.GetInterface("IDictionary");
                        if (pt.IsGenericType && pt.IsValueType == false && dic == null)
                        {
                            IList col = (IList)Activator.CreateInstance(pt);
                            // create an array of objects
                            foreach (object ob in (ArrayList)v)
                                col.Add(ParseDictionary((Dictionary<string, object>)ob));

                            oset = col;
                        }
                        else if (pt == typeof(byte[]))
                        {
                            oset = Convert.FromBase64String((string)v);
                        }
                        else if (pt.IsArray && pt.IsValueType == false)
                        {
                            ArrayList col = new ArrayList();
                            // create an array of objects
                            foreach (object ob in (ArrayList)v)
                                col.Add(ParseDictionary((Dictionary<string, object>)ob));

                            oset = col.ToArray(pi.PropertyType.GetElementType());
                        }
                        else if (pt == typeof(Guid) || pt == typeof(Guid?))
                            oset = new Guid("" + v);

                        else if (pt == typeof(DataSet))
                            oset = CreateDataset((Dictionary<string, object>)v);

                        else if (pt == typeof(Hashtable))
                            oset = CreateDictionary((ArrayList)v, pt);

                        else if (dic != null)
                            oset = CreateDictionary((ArrayList)v, pt);

                        else
                            oset = ChangeType(v, pt);

                        setter = GetSetter(pi);
                        setter(o, oset);
                    }
                }
            }
            return o;
        }

        private object CreateDictionary(ArrayList reader, Type pt)
        {
            IDictionary col = (IDictionary)Activator.CreateInstance(pt);
            Type[] types = col.GetType().GetGenericArguments();

            foreach (object o in reader)
            {
                Dictionary<string, object> values = (Dictionary<string, object>)o;

                object key;
                object val;


                if (values["k"] is Dictionary<string, object>)
                    key = ParseDictionary((Dictionary<string, object>)values["k"]);
                else
                    key = ChangeType(values["k"], types[0]);

                if (values["v"] is Dictionary<string, object>)
                    val = ParseDictionary((Dictionary<string, object>)values["v"]);
                else
                    val = ChangeType(values["v"], types[1]);

                col.Add(key, val);

            }

            return col;
        }

        public object ChangeType(object value, Type conversionType)
        {
            if (conversionType.IsGenericType &&
                conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {

                System.ComponentModel.NullableConverter nullableConverter
                    = new System.ComponentModel.NullableConverter(conversionType);

                conversionType = nullableConverter.UnderlyingType;
            }

            return Convert.ChangeType(value, conversionType);
        }

        private Hashtable CreateHashtable(ArrayList reader)
        {
            Hashtable ht = new Hashtable();

            foreach (object o in reader)
            {
                Dictionary<string, object> values = (Dictionary<string, object>)o;

                ht.Add(
                    ParseDictionary((Dictionary<string, object>)values["k"]),
                    ParseDictionary((Dictionary<string, object>)values["v"])
                );
            }
            return ht;
        }

        private DataSet CreateDataset(Dictionary<string, object> reader)
        {
            DataSet ds = new DataSet();

            // read dataset schema here
            string s = "" + reader["$schema"];
            TextReader tr = new StringReader(s);
            ds.ReadXmlSchema(tr);

            foreach (string key in reader.Keys)
            {
                if (key == "$type" || key == "$schema")
                    continue;
                object tb = reader[key];
                if (tb != null && tb.GetType() == typeof(ArrayList))
                {
                    ArrayList rows = (ArrayList)tb;
                    foreach (Dictionary<string, object> row in rows)
                    {
                        DataRow dr = ds.Tables[key].NewRow();
                        foreach (string col in row.Keys)
                        {
                            dr[col] = row[col];
                        }

                        ds.Tables[key].Rows.Add(dr);
                    }
                }
            }

            return ds;
        }
    }
}
